/******************************************************************************
 * $Id: find.c,v 1.87 2004/09/22 01:14:22 jimb Exp $
 * @file
 * @author	Jim Brooks http://www.jimbrooks.org
 * @brief	Functions for finding.
 *          Some functions for finding tags reside in tags.c.
 ******************************************************************************/

#include "common.h"
#include "find.h"

#include "text.h"
#include "list.h"
#include "module.h"
#include "notebook.h"
#include "widgets.h"
#include "handlers.h"
#include "statusbar.h"
#include "history.h"
#include "menu.h"
#include "tags.h"
#include "treefunc.h"

#define STR_REGEX_MATCH_TOO_BIG N_("(match was too big)")

/*****************************************************************************
 * Find all matches of a string under the cursor (of the text widget).
 *****************************************************************************/
void
FindMatchesUnderCursor( void )
{
   void FindMatchesUnderCursor___( void );

  /*
   * For stability, serialize this function.
   */
   if ( busy_FindMatchesUnderCursor ) { DebugMsg( "FindMatchesUnderCursor() is busy." ); return; }
   busy_FindMatchesUnderCursor = TRUE;
   FindMatchesUnderCursor___();
   busy_FindMatchesUnderCursor = FALSE;
}
void
FindMatchesUnderCursor___( void )
{
   GString*		pGstr		= NULL;
   gchar*		pText		= NULL;
   guint		textLen;
   guint		cursorIdx;
   gboolean		findCaseSensTextSaved;

  /*
   * Ensure the text widget is created and isn't empty.
   */
   if ( IsTextWidgetEmpty() )
   {
      PrintStatusbar( _("Text widget is empty" ));
      return;
   }

  /*
   * Obtain contents of text widget.
   */
   pText = ActiveTextWidgetContents( &textLen );
   if ( pText == NULL  ||  textLen > MAX_TEXT_BUFFER_LEN ) goto error;

  /*
   * Get index of cursor.
   */
   cursorIdx = OffsetAtCursor();// TextCursorIdx();
   if ( cursorIdx < 0  ||  cursorIdx > textLen ) goto error;

  /*
   * Scoop word at cursor.
   * Return if cursor is not over a word.
   */
   pGstr = ScoopWordAtCursor( MAX_STRING_LEN,
                              pText,
                              textLen,
                              cursorIdx );
   if ( pGstr == NULL )
   {
      PrintStatusbar( _("Please move the cursor over a string." ));
      goto quit;
   }

   /*-----*/
   findCaseSensTextSaved = findCaseSensText;
   findCaseSensText = TRUE;

   FindStringGlobal( pGstr->str,
                     FALSE,
                     FALSE );

   findCaseSensText = findCaseSensTextSaved;
   /*-----*/

  /*
   * Free mem, return.
   */
quit:
   if ( pGstr ) g_string_free( pGstr, TRUE );
   return;

  /*
   * Error occurred, but try to continue.
   */
error:
   WarningMalfunction();
   return;
}

/******************************************************************************
 * Find a string within a memory segment.
 * Can search forwards or backwards, case-[in]senstive, depending on
 * "findReverse", "findCaseSensText".
 * @param	pMatchLen (OUT)
 *          Length of match (N/A if NULL returned).
 * @param	pSought
 *          Pointer to the string to find.
 * @param	pSegment
 *          Pointer to segment to search.
 * @param	segmentLen
 *          Length of segment to search.
 * @param	findCaseSensText (GLOBAL)
 * @param	findReverse (GLOBAL)
 * @return	If found, pointer to the string, else NULL.
 ******************************************************************************/
gchar*
FindStringInSegment( gint*  pMatchLen,
                     gchar* pSought,
                     gchar* pSegment,
                     int    segmentLen )
{
   if (    pSought == NULL
        || pSegment == NULL
        || segmentLen < 0
        || segmentLen > MAX_TEXT_BUFFER_LEN )
      return NULL;

   if ( ! regex )
   {
      *pMatchLen = strlen( pSought );
      if ( ! findReverse )
         return FindSubstrSegment( pSegment,
                                   segmentLen,
                                   pSought,
                                   findCaseSensText );
      else
         return FindSubstrReverse( pSegment + segmentLen - 1,
                                   pSegment,
                                   pSought,
                                   findCaseSensText ? strncmp : strncasecmp );
   }
   else
   {
      gchar* pMatch;
      pMatch = FindRegexSegment( pMatchLen,
                                 pSegment,
                                 MAX(segmentLen-1,1),
                                 pSought, /* regex pattern */
                                 findCaseSensText,
                                 findReverse,
                                 regexScope==REGEX_SCOPE_LINE ? REG_NEWLINE : 0 );
     /*
      * Pretend nothing was found if match is too big.
      */
      if ( *pMatchLen < MAX_REGEX_MATCH_LEN )
         return pMatch;
      else
      {
         PrintStatusbar( _(STR_REGEX_MATCH_TOO_BIG ));
         return NULL;
      }
   }
}

/******************************************************************************
 * Find the next string in the text widget.
 * First does a search starting from the current position.
 * If unsuccessful, tries a wrapped search.
 * If found, the matching string will be scrolled to and highlighted. 
 * @param	pSoughtText (GLOBAL)
 *          Pointer to the previous sought string.
 * @param	findCaseSensText (GLOBAL)
 *          Whether to be case-sensitive.
 * @return	TRUE if found.
 ******************************************************************************/
gboolean
FindNextStringInTextWidget( void )
{
  /*
   * Has Find/Text been selected previously?
   */
   if ( soughtType == MENU_ITEM_FIND_TEXT  &&  pSoughtText )
   {
      return FindStringInTextWidget( pSoughtText->str,
                                     TRUE,		/* do wrap */
                                     FALSE,		/* don't be quiet */
                                     TRUE,      /* do scroll to match */
                                     NULL );    /* don't care about line # */
   }

  /*
   * Find/Text has not been selected yet.
   */
   return FALSE;
}

/*****************************************************************************
 * Find a tag in clist widget.
 * Warn user if there are multiple matches.
 * @return Row # of first match.
 *****************************************************************************/
int
FindTagCheckMultipleMatches( char*    pTagName,
                             gboolean seekingFuncLikeTag,
                             char*    pModuleName,
                             int      rowStarting,
                             gboolean exact,
                             gboolean caseSens,
                             gboolean selectNotebookPage,
                             char*    pMsgMultipleMatches )
{
   gint		rowFirstMatch;
   gint		row;

#ifdef HYPERSRC_PEDANTIC
   g_assert( pWidgetClistTags );
#endif
   g_return_val_if_fail( ! busy_PopulateTagsClistWidget, ROW_NONE );

   ClearStatusbar();

  /*
   * Try to find first match.  If found, then select row.
   */
   rowFirstMatch = \
   SearchClistWidget( pTagName,
                      seekingFuncLikeTag,
                      pModuleName,
                      pWidgetClistTags,
                      rowStarting,
                      exact,
                      caseSens,
                      TRUE, /* be quiet */
                      selectNotebookPage, 
                      TRUE	/* do select row */ );

   if ( rowFirstMatch == ROW_NONE )
   {
      UndoRecordCursorInTagHistory();

      if ( exact )
         PrintStatusbarThirds( "Tag '", pTagName, _("' not found (exact search)." ));
      else
         if ( caseSens )
            PrintStatusbarThirds( "Tag '",pTagName, _("' not found (fuzzy/case search)." ));
         else
            PrintStatusbarThirds( "Tag '",pTagName,_("' not found (fuzzy search)." ));

      return ROW_NONE;
   }

  /*
   * Check if there are multiple matches.
   * Print a message if so.
   */
   row = \
   SearchClistWidget( pTagName,
                      seekingFuncLikeTag,
                      pModuleName,
                      pWidgetClistTags,
                      widgetTagsClistSelectedRow,
                      exact,
                      caseSens,
                      TRUE,	/* quiet */
                      FALSE,/* don't update notebook */
                      FALSE	/* don't select row */ );

   if ( row != ROW_NONE  &&  row != widgetTagsClistSelectedRow )
      PrintStatusbar( pMsgMultipleMatches );

   return rowFirstMatch;
}

/*****************************************************************************
 * Repeat the previous Find/Text or Find/Tag.
 *****************************************************************************/
void
FindNextTextOrTag( void )
{
  /*
   * Repeat Find/Text ?
   */
   if ( soughtType == MENU_ITEM_FIND_TEXT )
   {
      FindNextStringInTextWidget();
      return;
   }

  /*
   * Repeat Find/Tag ?
   */
   if ( soughtType == MENU_ITEM_FIND_TAG )
   {
      FindNextTag();
      return;
   }

   PrintStatusbar(_( "No previous search." ));
   return;
}

/*****************************************************************************
 * Repeat the last Find/Tag.
 *
 * @param	pSoughtTag (GLOBAL)
 *          Pointer to the name of sought tag.
 * @param	[other find state] (GLOBAL)
 * @return	If found, the row in which the match was found (row will be >=0),
 *          else returns ROW_NONE.
 *****************************************************************************/
gint
FindNextTag( void )
{
#ifdef HYPERSRC_PEDANTIC
   g_assert( pWidgetClistTags );
#endif
   g_return_val_if_fail( ! busy_PopulateTagsClistWidget, ROW_NONE );

  /*
   * Has Find/Tag been selected previously?
   */
   if ( soughtType == MENU_ITEM_FIND_TAG  &&  pSoughtTag->str )
   {
      return \
      SearchClistWidget( pSoughtTag->str,
                         SEEKING_ANY_TAG,
                         NULL,
                         pWidgetClistTags,
                         widgetTagsClistSelectedRow,
                         findExactTag,
                         findCaseSensTag,
                         FALSE,	/* be loud */
                         autoSelectNotebookPage,
                         TRUE	/* do select row */ );
   }

  /*
   * Find/Tag has not been selected yet.
   */
   return ROW_NONE;
}

/******************************************************************************
 * Find a string in the text widget.
 *
 * First does a search starting from the current position.
 * If unsuccessful, can try a wrapped search (if requested).
 * If found (and requested), the matching string will be scrolled to and highlighted. 
 *
 * @param	pSought
 *          Pointer to the string to find.
 * @param	tryWrap
 *          If true then wrap search to top if necessary.
 * @param	quiet
 *          If true then don't print to statusbar.
 * @param	scroll
 *          If true then match will be scrolled to.
 * @param	pReturnLineNumFound.
 *          Pass NULL if not wanted.
 *          Ignore this if FALSE is returned by this function.
 * @param	findCaseSensText (GLOBAL)
 * @param	findReverse (GLOBAL)
 * @return	TRUE if found.
 ******************************************************************************/
gboolean
FindStringInTextWidget( gchar*   pSought,
                        gboolean tryWrap,
                        gboolean quiet,
                        gboolean scroll,
                        gint*    pReturnLineNumFound )
{
   gchar*	pText		= NULL;
   guint	textLen;

  /*
   * Return if Find/Text (local) was selected but the text widget is empty.
   * A similar check for Find/Tag is unnecessary because there
   * should always be a list of tags.
   */
   if ( soughtType == MENU_ITEM_FIND_TEXT  &&  IsTextWidgetEmpty() )
   {
      PrintStatusbar(_("Text widget is empty (try selecting 'global' checkbox)."));
      return FALSE;
   }

   if ( ! quiet ) PrintStatusbarThirdsNoTimeout(_("Searching for '"), pSought, "'..." );

  /*
   * Obtain contents of text widget.
   */
   pText = ActiveTextWidgetContents( &textLen );
g_return_val_if_fail( pText  &&  textLen > 0, FALSE );

  /*
   * Do the actual search.
   */

   if ( pText  &&  textLen > 0  &&  textLen < MAX_TEXT_BUFFER_LEN )
      return FindStringInTextWidget_( pSought,
                                      tryWrap,
                                      quiet,
                                      scroll,
                                      pReturnLineNumFound,
                                      pText,
                                      textLen );
   else
      return FALSE;
}

/******************************************************************************
 * Primitive of FindStringInTextWidget().
 * FindStringInTextWidget() is responsible for managing the text memory buffer
 * that this primitive searches thru.
 ******************************************************************************/
gboolean
FindStringInTextWidget_( gchar*   pSought,
                         gboolean tryWrap,
                         gboolean quiet,
                         gboolean scroll,
                         gint*    pReturnLineNumFound,
                         gchar*   pText,
                         gint     textLen )
{
   gchar*	pMatch;
   gint     matchLen;
   gint		idxPos, charidxPos;

  /*
   * Compute the index of the char after the current position.
   */

   charidxPos = OffsetAtCursor();   // utf8 offset
   
g_return_val_if_fail( charidxPos < textLen, FALSE );

    //// convert from char length to byte length
   idxPos = UTF8IndexToAsciiIndex(pText, charidxPos);

#ifdef HYPERSRC_DEBUG
   const gchar * pIdxPosT = g_utf8_offset_to_pointer(pText, charidxPos);
   gint idxPosT = (gint)(pIdxPosT - pText); 
   g_assert(idxPosT == idxPos );    
#endif

g_return_val_if_fail( idxPos < textLen, FALSE );

  /*
   * Begin the search in the segment from the cursor,
   * direction depending on "findReverse".
   *
   * Skip this search if the cursor is at the start or end,
   * depending on search direction.
   */
   if (    (idxPos < textLen  &&  ! findReverse )
        || (idxPos >       0  &&    findReverse ) )
   {
      if ( ! findReverse )
         pMatch = FindStringInSegment( &matchLen,
                                       pSought,
                                       //由于每次搜索到一个Word后并选中它，光标就置
                                       //于该词的首字母处，导致每次都不能往下搜索，故
                                       //采用加1的方法来规避。
                                       pText + idxPos +1,
                                       textLen - idxPos );
      else
         pMatch = FindStringInSegment( &matchLen,
                                       pSought,
                                       pText,
                                       idxPos );
   }
   else
   {
     /*
      * Cursor is at top or bottom, so a wrap search is needed.
      */
      pMatch = NULL;
   }

   if ( pMatch )
   {
      g_return_val_if_fail( pMatch >= pText, FALSE );

     /*
      * Found the string.
      * If requested, scroll/highlight string.
      */
      MostlyClearStatusbar();
      if ( scroll )
      {
         HighlightTextString( pText,
                              pMatch - pText,
                              matchLen );
      }
      if ( pReturnLineNumFound )
      {
         *pReturnLineNumFound = LineIdx2LineNum( pText,
                                                 pMatch - pText );
      }

     /*
      * Done.
      */
      return TRUE;
   }

  /*
   * Not found.  Try a wrapped search (if specified to do so).
   * Is the cursor already at the top/bottom such that a full search was done?
   * If so, skip this wrapped search.
   */
   if ( ! tryWrap ) return FALSE;

   if ( idxPos > 0  &&  ! findReverse )
   {
     /*
      * Forward wrapped search.
      */
      pMatch = FindStringInSegment( &matchLen,
                                    pSought,
                                    pText,
                                    idxPos );
   }
   else if ( idxPos < textLen  &&  findReverse )
   {
     /*
      * Reverse wrapped search.
      * Ensure parms to pass will be correct.
      */
g_return_val_if_fail( textLen - idxPos >= 0, FALSE );
      if ( textLen - idxPos >= 0 )
         pMatch = FindStringInSegment( &matchLen,
                                       pSought,
                                       pText + idxPos,
                                       textLen - idxPos );
      else
         pMatch = NULL;
   }
   else
   {
      pMatch = NULL;
   }
   if ( pMatch )
   {
      g_return_val_if_fail( pMatch >= pText, FALSE );

     /*
      * Found the string.
      * Scroll to, and highlight, the string.
      */
      if ( ! quiet ) PrintStatusbarThirds( _("Search wrapped for '"), pSought ,"'");
      HighlightTextString( pText,
                           pMatch - pText,
                           matchLen );

     /*
      * Done.
      */
      return TRUE;
   }

  /*
   * String was definitely not found.
   */
   if ( ! quiet  &&  CompareStrings( GetStatusbarMsg(), _(STR_REGEX_MATCH_TOO_BIG )) )
      PrintStatusbarThirds( "'", pSought, _("' wasn't found." ));

   return FALSE;
}

/******************************************************************************
 * Main function for Find/Global that:
 *
 * - Finds all instances of a string in all modules.
 * - Builds a glist of tag_t structs defining lines which contain sought string.
 * - Creates a notebook page containing a list widget, so that the user
 *   can select any match.
 *
 * @param	pSought
 *          Pointer to the string to find.
 * @param	quiet
 *          If true then don't print to statusbar.
 * @param	pGlistFoundTags (OUT/GLOBAL)
 *          List of tagged lines where matches were found.
 * @param	findCaseSensText (GLOBAL)
 ******************************************************************************/
gboolean
FindStringGlobal( gchar*   pSought,
                  gboolean quiet,
                  gboolean stopIfFindDialogClosed )
{
   int res;
   gboolean FindStringGlobal___( gchar*   _pSought,
                                 gboolean _quiet,
                                 gboolean _stopIfFindDialogClosed );

  /*
   * For stability, serialize this function.
   */
   if ( busy_FindStringGlobal ) { DebugMsg( "FindStringGlobal() is busy." ); return FALSE; }
   busy_FindStringGlobal = TRUE;
   res = FindStringGlobal___( pSought, quiet, stopIfFindDialogClosed );
   busy_FindStringGlobal = FALSE;
   return res;
}
gboolean
FindStringGlobal___( gchar*   pSought,
                     gboolean quiet,
                     gboolean stopIfFindDialogClosed )
{
   gboolean notCanceled;

   PrintStatusbarNoTimeout(_("Searching globally (please wait)..." ));

  /*
   * Search all modules for all instances of sought string.
   * Output will be in pGlistFoundTags.
   */
   notCanceled = \
   BuildListOfFoundStrings( pSought,
                            quiet,
                            stopIfFindDialogClosed );

  /*
   * Were any matches found?
   */
   if ( pGlistFoundTags == NULL )
   {
      PrintStatusbar(_("Nothing found (global search).") );
      return FALSE;
   }

  /*
   * Matches were found.
   * Create a notebook page showing the results of the search.
   */
   BuildNotebookPageWithFindResults();

   if ( notCanceled) MostlyClearStatusbar();
   FindDialogClose();

   return TRUE;
}

/******************************************************************************
 * Find all instances of a string in all modules.
 * Build a glist of tag_t structs defining lines which contain sought string.
 * If conserveMemory then all module buffers will be freed.
 *
 * @param	pSought
 *          Pointer to the string to find.
 * @param	quiet
 *          If true then don't print to statusbar.
 * @param	pGlistFoundTags (OUT/GLOBAL)
 *          List of tagged lines where matches were found.
 * @param	findCaseSensText (GLOBAL)
 * @param	searchMethodFoundPage (OUT/GLOBAL)
 *          Remembers if the results in the found page were the result
 *          of a regular or regex search.
 * @return	TRUE if search completed (wasn't canceled/aborted).
 ******************************************************************************/
gboolean
BuildListOfFoundStrings( gchar*   pSought,
                         gboolean quiet,
                         gboolean stopIfFindDialogClosed )
{
   GList*	pModuleName			= NULL;
   char*	pModuleBuffer		= NULL;
   guint	moduleSize;
   gchar*   pFound              = NULL;
   gchar*   pTextRemaining		= NULL;
   int      modules             = 0;
   gint     loops				= 0;
   gint     percentageShown     = 0;
   gint     percent;
   gint     matchLen;

   modules = g_hash_table_size( pHashModules );

  /*
   * Remember if the results in the found page were the result
   * of a regular or regex search.
   */
   searchMethodFoundPage = regex;

  /*
   * Free previous glist.
   */
   if ( pGlistFoundTags ) g_list_free( pGlistFoundTags );
   pGlistFoundTags = NULL;

  /*
   * Search thru all modules.
   * Iterate for each name in the list "pGlistModuleNames".
   */
   for ( pModuleName = pGlistModuleNames;
         pModuleName;
         pModuleName = g_list_next( pModuleName ) )
   {
      ++loops;

      if ( (loops & 0x3f) == 0 )
      {
         percent = (int) ( (float)loops / (float)modules * 100 );
         if ( (percent - percentageShown) >= 5 )
         {
            percentageShown = percent;
            StatusbarShowPercentage(_("Modules searched (please wait): __%."), percent );
         }
      }

      ProcessPendingEvents();

     /*
      * Stop if find dialog was closed (if requested so).
      */
      if ( stopIfFindDialogClosed  &&  pWidgetDialogFind == NULL )
      {
         PrintStatusbar(_("Search canceled." ));
         return FALSE;
      }

     /*
      * In order to search, load the next module into a memory buffer.
      */
      pModuleBuffer = ModuleBuffer( (gchar*)pModuleName->data,
                                    &moduleSize );
      if ( pModuleBuffer == NULL  ||  moduleSize == 0 )
         goto iterate; /* NULL can happen if file is empty */

     /*
      * Find all matches in this module.
      */
      pTextRemaining = pModuleBuffer;
      for (;;)
      {
        /*
         * So that a prolonged search won't seem to have hanged the program.
         */
         PeriodicallyProcessPendingEvents();

        /*
         * Search remaining text in buffer for the sought string.
         */
         pFound = FindStringInSegment( &matchLen,
                                       pSought,
                                       pTextRemaining,
                                       moduleSize - (pTextRemaining - pModuleBuffer) );

         #ifdef HYPERSRC_PEDANTIC
         if (    (loops > MAX(MAX_LOOPS, modules))
              || ( pFound != NULL  &&  pFound < pModuleBuffer)
              || (moduleSize - (pTextRemaining - pModuleBuffer) < 0 )
              || (pTextRemaining > (pModuleBuffer + moduleSize)) )
         { Warning("Good programmers are hard to find.  Aborting BuildListOfFoundStrings()"); return FALSE; }
         #endif

        /*
         * Was it found?
         */
         if ( pFound )
         {
            gchar line_str[MAX_LINE_LEN+1]={0};
            gint      line_len=0;

            gint      line_no  = LineIdx2LineNum( pModuleBuffer, pFound - pModuleBuffer );
            const gchar * line_str_start= ToLine(pModuleBuffer, line_no -1, MAX_LINE_LEN);
            gchar * line_str_end=g_strstr_len(line_str_start,MAX_HAYSTACK_LEN,"\n");
            if ( NULL == line_str_end )
            {
                line_len = strlen(line_str_start);
                g_print("%d\n",line_len);
                memcpy(line_str, line_str_start, line_len);
                line_str[line_len]='\0';
             }
             else
             {
                 line_len = (gint) (line_str_end - line_str_start);
                 g_print("%d\n",line_len);
                 memcpy(line_str, line_str_start, line_len);
                 line_str[line_len]='\0';
             }
             // erase the SPACE 
             gchar * line_str2 = line_str;
             while ( ' ' == *line_str2 || '\t' == *line_str2 )
             {
                 ++line_str2;
             }
           /*
            * Found, create a tag for this line, append tag to list.
            */
            AppendFoundTagsList( line_str2, //pSought,
                                 (gchar*)pModuleName->data,
                                 line_no);

           /*
            * Point after this match. 
            * Break if end of buffer.
            */
            pTextRemaining = pFound + matchLen;
            if ( (pTextRemaining - pModuleBuffer) >= moduleSize ) break;

           /*
            * Continue searching for more matches in this module.
            */
            continue;
         }
         else
         {
           /*
            * Not found in this module, break to next module.
            */
            break;
         }
      }

     /*
      * Iterate to next module.
      */
      iterate:
      FreeModuleBuffer( (gchar*)pModuleName->data );
   }

   if ( pModuleName != NULL )
      FreeModuleBuffer( (gchar*)pModuleName->data );

  /*
   * Done.
   */
   MostlyClearStatusbar();
   return TRUE;
}

/******************************************************************************
 * Create a tag_t struct for a line with a matching string.
 * Append the tag_t struct to the global glist "pGlistFoundTags"
 * (this glist will be created if empty).
 *
 * @param	pTagName
 *          Written to tag_t.pName.
 * @param	pModuleName
 *          Written to tag_t.pModule.
 * @param	lineNum
 *          Written to tag_t.pLine and tag_t.lineInt
 * @param	pGlistFoundTags (OUT/GLOBAL)
 ******************************************************************************/
FUNC_X
AppendFoundTagsList( gchar* pTagName,
                     gchar* pModuleName,
                     gint   lineNum )
{
   tag_t*	pTag;

  /*
   * Allocate a tag_t struct.
   */
   pTag = AllocTagStruct();

  /*
   * Fill the tag_t struct.
   */
   FillTagStruct( pTag,
                  pTagName,
                  lineNum,
                  "found",
                  pModuleName );

  /*
   * Add the tag_t struct to the list.
   */
   pGlistFoundTags = g_list_append( pGlistFoundTags, (gpointer)pTag );
   g_return_val_if_fail( pGlistFoundTags, FUNC_FAIL );

   return FUNC_OK;
}

/******************************************************************************
 * Create a notebook page with the results of a search.
 *
 * @param	pGlistFoundTags (GLOBAL)
 *          List that defines the search results.
 ******************************************************************************/
void
BuildNotebookPageWithFindResults( void )
{
//   GtkWidget* pWidgetLabel;

  /*
   * Create a clist widget to hold each tagged matching line (unless it already exists).
   */
   if ( pWidgetClistFound == NULL )
   {
      CreateInvisibleTagsClistWidget( &pWidgetScrolledFound,
                                      &pWidgetClistFound,
                                      TAG_COL_LAYOUT_TAG_MODULE_LINE,
                                      (PFUNC)FoundClistWidgetRowSelectCallback,
                                      (PFUNC)FoundClistWidgetRowUnselectCallback );
      gtk_widget_show( pWidgetScrolledFound );
      gtk_widget_show( pWidgetClistFound );
   }
   else
   {
     /*
      * Clist widget already exists, remove all of its rows.
      */
      gtk_clist_clear( GTK_CLIST(pWidgetClistFound) );
   }

  /*
   * Add the tags clist widget (contained by a scrolled widget) to notebook.
   */
   if ( ! notebookPageFound )
   {
      //pWidgetLabel = gtk_label_new( _("found" ));
      GtkWidget *pImage = gtk_image_new_from_file(IMAGE_DIR"found.gif");
      gtk_notebook_append_page( GTK_NOTEBOOK(pWidgetNotebook),
                                pWidgetScrolledFound,
                                pImage );
      notebookPageFound = TRUE;
   }

  /*
   * Populate the clist widget with the search results.
   */
   gtk_clist_freeze( GTK_CLIST(pWidgetClistFound) );
   g_list_foreach( pGlistFoundTags,
                   AddRowToFoundClistWidget,
                   NULL );
   gtk_clist_thaw( GTK_CLIST(pWidgetClistFound) );

  /*
   * Select the notebook page.
   */
   gtk_notebook_set_page( GTK_NOTEBOOK(pWidgetNotebook),
                          NotebookPageFound() );

  /*
   * Install handlers when notebook page gains/loses focus.
   */
   InstallFocusHandlers( pWidgetClistFound );

   return;
}

/******************************************************************************
 * Using an element in the clist of tagged search results, add a row
 * to the clist widget containing search results.
 *
 * Each row is associated with a tag_t struct (via the GTK "row data" mechanism).
 *
 * @param	pData
 *          Pointer to a tag_t struct.
 * @param	pUnused
 ******************************************************************************/
void
AddRowToFoundClistWidget( gpointer pData,
                          gpointer pUnused )
{
   tag_t*		pTag	= pData;
   gchar*       szRow[WIDGET_FOUND_CLIST_COLUMNS];
   int			row;
   static char	strLine[30];

  /*
   * GTK+ adds to a clist a duplicate of a string passed from an app
   * (so passing this static string is OK).
   */
   sprintf( strLine, "%d", pTag->lineInt );

  /*
   * Actually add a row to the clist widget.
   */
   szRow[0] = pTag->pName;
   szRow[1] = pTag->pModuleName;
   szRow[2] = strLine;
   row = gtk_clist_append( GTK_CLIST(pWidgetClistFound),
                           szRow );

  /*
   * Associate the pointer to the tag_t struct with the row.
   * The pointer will be needed later when the user selects a row.
   */
   gtk_clist_set_row_data( GTK_CLIST(pWidgetClistFound),
                           row,
                           pData );
   return;
}

/******************************************************************************
 * Callback for when a row is selected in the search results clist widget.
 *
 * The purpose of this callback is to load the text widget
 * with the module associated with the selected tag.
 *
 * Will automatically select the corresponding row in the modules clist,
 * thus indirectly calling ModulesClistWidgetRowSelectCallback().
 *
 * @param	pWidget
 *          Pointer to clist widget.
 * @param	row
 *          Which row was selected.
 * @param	col
 *          Which column was selected.
 * @param	GdkEventButton
 *          (not used here?)
 * @param	pData
 *          (not used here, NULL is passed)
 * @param	searchMethodFoundPage (GLOBAL)
 *          Records whether a normal or regex search was done.
 ******************************************************************************/
void
FoundClistWidgetRowSelectCallback( GtkWidget*      pWidget,
                                   gint            row,
                                   gint            col,
                                   GdkEventButton* pEvent,
                                   gpointer	       pData )
{
   tag_t*		pTag;
   GtkWidget*	pWidgetTextActivePrev   = pWidgetTextActive;

  /*
   * Prepare to load the module containing the tag, and move the cursor over the tag.
   *
   * Obtain the pointer to the tag_t struct that is associated
   * with the selected row in the tags clist widget.
   */
   pTag = (tag_t*)gtk_clist_get_row_data( GTK_CLIST(pWidget), row );
g_return_if_fail( pTag );

  /*
   * Should the tagged line be shown in the main or aux text widget?
   */
   SwitchActiveTextWidget( DONT_SWITCH_UNLESS_AUX_TEXT_KEY );

  /*
   * Load the selected module (ie source file) into the text widget.
   * Move the cursor to the line that is tagged.
   */
   if ( LoadTextWidgetWithModule( pTag->pModuleName,
                                  pTag->lineInt ,   //- 1,
                                  row,
                                  DONT_RELOAD )
        != FUNC_OK )
   {
     /*
      * Oops, unable to load the module.
      * (A warning has already been displayed.)
      */
      goto quit;
   }

  /*
   * Highlight the tag in the text widget.
   */
   HighlightWordInCurrentLine( pTag->pName,
                               findCaseSensText,
                               searchMethodFoundPage,
                               TRUE );

  /*
   * Select the corresponding row in the modules clist.
   * Set a flag to inform the module's select_row callback to return early,
   * without affecting other colateral state such as the previous location etc.
   */
   returnEarlyModuleSelectCallback = TRUE;
   SearchClistWidget( pTag->pModuleName,
                      SEEKING_ANY_TAG,
                      NULL,
                      pWidgetClistModules,
                      0,
                      TRUE, /* exact match */
                      findCaseSensModule,
                      TRUE, /* be quiet */
                      FALSE,/* don't affect notebook */
                      TRUE	/* do select row */ );
   returnEarlyModuleSelectCallback = FALSE;

   ScheduleUpdateFuncTree();

quit:
  /*
   * Restore the active text widget.
   */
   SetActiveTextWidget( pWidgetTextActive = pWidgetTextActivePrev );

   return;
}

/******************************************************************************
 * Callback for when a row is UNselected in the search results clist widget.
 ******************************************************************************/
void
FoundClistWidgetRowUnselectCallback( GtkWidget*      pWidget,
                                     gint            row,
                                     gint            col,
                                     GdkEventButton* pEvent,
                                     gpointer	     pData )
{
  /*
   * (Yep, is a NOP.)
   */
   return;
}
